//cd /d D:\Game\MC\Minecraft ws\study
/* jshint esversion: 7 */
/*jshint -W069 */
//    ______   __                    __      __       __   ______    ______  
//   /      \ |  \                  |  \    |  \  _  |  \ /      \  /      \ 
//  |  $$$$$$\| $$____    ______   _| $$_   | $$ / \ | $$|  $$$$$$\|  $$$$$$\
//  | $$   \$$| $$    \  |      \ |   $$ \  | $$/  $\| $$| $$___\$$| $$___\$$
//  | $$      | $$$$$$$\  \$$$$$$\ \$$$$$$  | $$  $$$\ $$ \$$    \  \$$    \ 
//  | $$   __ | $$  | $$ /      $$  | $$ __ | $$ $$\$$\$$ _\$$$$$$\ _\$$$$$$\
//  | $$__/  \| $$  | $$|  $$$$$$$  | $$|  \| $$$$  \$$$$|  \__| $$|  \__| $$
//   \$$    $$| $$  | $$ \$$    $$   \$$  $$| $$$    \$$$ \$$    $$ \$$    $$
//    \$$$$$$  \$$   \$$  \$$$$$$$    \$$$$  \$$      \$$  \$$$$$$   \$$$$$$ 
//

const ver = [4, 5, 2]; //版本号
try {
    if (mc != undefined){
        global.RUNMODE = 'LLSE';
    }
}
catch(e){
    global.RUNMODE = 'NODEJS';
}


const Websocket = require("ws");
const url = require('url');
global.fs = require("fs");
const getPixels = require("get-pixels");
global.convert = require('color-convert');
global.CronJob = require('cron').CronJob;

global.ColorDif = require("./lib/ColorDifferences.js");
global.logger = require("./lib/logger.js");
const ClientClass = require('./lib/ClientClass.js');
const syncFunc = require('./lib/syncFunc.js');
const mapcolor_lab_flat = require("./lib/mapcolor_lab_flat.js"); //平面地图色
const mapcolor_lab = require("./lib/mapcolor_lab.js"); //立体地图色
const getPixelsMap = require("./lib/getPixelsMap.js"); //生成像素画的功能函数

if (RUNMODE == 'NODEJS'){
    global.Level  = require('level').Level;
    global.KVDatabase = require('./lib/LevelDBClass.js').LevelDBClass;
    global.JsonConfigFile =  require("./lib/json.js").JsonConfigFile;
}

//-------------------------------------------------------------
// === WS协调服务端配置 ===
let config_json = new JsonConfigFile('./plugins/chatwss/WSSConfig.json', JSON.stringify({
    "CONFIG": "此为Websocket协调服务端配置文件",
    "Websocket": {
        "ServerName": "WS协调服务端", // WS协调服务端名称
        "Port": 9001,                // WS协调服务端端口
        "ConnectKey": "FlySteve",    // WS协调服务端连接密钥
    },
    "Chat": {
        "chatDisplay": true,                       // WS协调服务端控制台聊天显示
        "chatLog": true,                           // 聊天日志, 记录所有玩家聊天日志到./log/chatwss/PlayerChat/
        "chatHeader": "",                          // 聊天前缀, 玩家在聊天内容开头添加此字符进行云端聊天。
        "tipHeader": "§d『§b${ServerName}§d』§r",  // 聊天显示头衔, ${ServerName} 替换为WS协调服务端名称。
    },
    "MapDrawing":{
        "imgFilePath": "./plugins/CustomMap/.img"   // CustomGetMap图片库路径 ./plugins/CustomMap/.img
    },
    "Sync": {
        "SyncInventoryDB": "主服存档",               // 跨服同步：玩家数据库保存位置: '主服存档', '外置中心数据库'
        "SyncInventoryDBPath": "./plugins/chatwss", // 跨服同步：外置中心数据库根路径
        "BackupTime": ["0 0 22 * * *"],               // 外置中心数据库备份时间, 可写为列表形式（Cron表达式，默认每晚22:00, 务必与存档备份同步）
        "BackupDir": "./backup/chatwss",            // 外置中心数据库备份路径（时间上跟随存档备份）
        "BackupRemainDays": 15,                     // 外置中心数据库备份留存天数。
        "SyncMode": "center",                       // 跨服同步：（此配置无效）中心同步: 'center' 分布式同步:'distributed' 
    }

}, null, 4));

global.ServerConfig = JSON.parse(config_json.read());

// 配置载入
logger.setConsole(true, 4); //日志等级, 5为debug模式
const ServerName = ServerConfig.Websocket.ServerName;
const Port = ServerConfig.Websocket.Port; 
const ConnectKey = ServerConfig.Websocket.ConnectKey;
var chatDisplay = ServerConfig.Chat.chatDisplay;
const chatLog = ServerConfig.Chat.chatLog;
const chatHeader = ServerConfig.Chat.chatHeader;
global.tipHeader = ServerConfig.Chat.tipHeader.replaceAll("${ServerName}", ServerName);
const imgFilePath = ServerConfig.MapDrawing.imgFilePath;
global.SyncInventoryDB = ServerConfig.Sync.SyncInventoryDB; 
if (!["主服存档", "外置中心数据库"].includes(SyncInventoryDB)){
    return logger.error(`[WSSConfig.json] WS服务端配置错误, 玩家数据库保存位置("SyncInventoryDB") 应填 "主服存档" 或 "外置中心数据库"`);
}
global.SyncInventoryDBPath = ServerConfig.Sync.SyncInventoryDBPath; 
global.SyncMode = ServerConfig.Sync.SyncMode;
global.SyncBackupTime= ServerConfig.Sync.BackupTime;              // 外置中心数据库备份时间（Cron表达式，默认每晚22:00）
global.SyncBackupDir= ServerConfig.Sync.BackupDir;                // 外置中心数据库备份路径（时间上跟随存档备份）
global.SyncBackupRemainDays = ServerConfig.Sync.BackupRemainDays;  // 外置中心数据库备份留存天数。

const events = ["PlayerMessage"]; // 需要订阅事件的列表 "PlayerTransform", "PlayerLeave" | 原版MC支持的事件类型

// 其他配置
//const DebugMode = false; // debug模式
const HelpInfo = `§b<WS协调服务端帮助手册>
§e# 云聊天:§r 
§e# 查询WS在线连接:§r .online
§e# 生成坐标定位:§r ./loc
§e# 像素画生成:§r ./paint 图片名.jpg [flat(平面)|uneven(立体)] [步长:32-128的数字]
§e# 终止生成进程:§r ./stoptask`;
//-------------------------------------------------------------


//全局变量 与函数
var wss; // ws服务器
global.mcws = new ClientClass.WssClients(); // Websocket服务器所有的连接
var LocalClientState = false;            // 是否作为BDSNodejs插件运行，如是，则启用Nodejs插件的本地客户端
global.CENTERDATA_db;                   // 玩家主数据库（不可删，存背包、计分板、llmoney等信息）
global.CENTERTemp_db;               // 玩家信息数据库（如无玩家在意外退出状态，可随时删）

var now = () =>{return ['[',new Date().toTimeString().slice(0, 8),']'].join('').replace(/:/g, "：");}; //时间函数
var msgHeader = (sender, serverName) =>{
    if (serverName != undefined){return `§b[${serverName}]§r<${sender}>§r`;} //玩家聊天，聊天栏消息格式 ${now()} 为当前时间
    return `§b<${sender}>§r`;
};
var msgLogHeader = (sender) =>{return `${now()}<${sender}>`;};
var jsonDisplay = false;
global.yoyorobot = {};

//=======================================
//判断是否作为llseNodejs插件运行 或是 node运行
if (RUNMODE == 'LLSE'){
    // if (SyncInventoryDB == '外置中心数据库'){
    //     logger.warn('使用 外置中心数据库 存储玩家数据必须独立运行WS协调服务端。');
    //     SyncInventoryDB == '主服存档';
    // }
    mc.listen("onServerStarted", ()=>{
        // 初始化外置玩家数据库
        if (SyncInventoryDB == '外置中心数据库'){
            logger.info(`跨服同步使用外置玩家数据库, 正在初始化数据库...`);
            CENTERDATA_db = new KVDatabase(`${SyncInventoryDBPath}/CENTERDATA`);
            CENTERTemp_db = new KVDatabase(`${SyncInventoryDBPath}/CENTERTemp`);
            // playersNBT_db = new KVDatabase(`${SyncInventoryDBPath}/playersNBT`);
            // playersOtherData_db =  new KVDatabase(`${SyncInventoryDBPath}/playersOtherData`);
            // playerInfo_db = new KVDatabase(`${SyncInventoryDBPath}/playerInfo`);
            syncFunc.autoBackup(); // 启动自动备份
        }
        

        // 开启Websocket服务端
        wss = new Websocket.Server({host:"0.0.0.0", port: Port, verifyClient: ClientVerify});
        wss.on('connection', wsCallback);
        let taskid = setInterval(function(){
            if (wss.address() == null){return;}
            clearInterval(taskid);
            logger.info(`${ServerName} 已启动(ver:${String(ver)}) |地址：${wss.address().address} |协议: ${wss.address().family} |端口: ${wss.address().port}`);
            
            //开始本地BDS客户端连接
            let ChatWsClient = require('./lib/ChatWsClient_node.js');
            wsCallback(websocket.ws); //本地客户端登录ws协调服务端
            //logger.info("== 本地BDS已连接到云端 ==");
            LocalClientState = true; //已开始本地客户端(./plugins/nodejs/chatwss/lib/ChatWsClient.js)
        }, 1000);
    });
    mc.listen("onPreJoin",(pl)=>{
        if (websocket?.ws == null){
            logger.warn(`<${pl.realName}> 主服正在启动...Websocket未初始化, 请稍后尝试登录`);
            pl.disconnect(`§c[${ServerName}] 主服正在启动...Websocket未初始化, 请稍后尝试登录`);
            return false;
        }
    });
}

if (RUNMODE == 'NODEJS'){
    logger.info('#################################################################');
    logger.info(' ██████╗ ██╗  ██╗  █████╗  ████████╗ ██╗    ██╗ ███████╗ ███████╗');
    logger.info('██╔════╝ ██║  ██║ ██╔══██╗ ╚══██╔══╝ ██║    ██║ ██╔════╝ ██╔════╝');
    logger.info('██║      ███████║ ███████║    ██║    ██║ █╗ ██║ ███████╗ ███████╗');
    logger.info('██║      ██╔══██║ ██╔══██║    ██║    ██║███╗██║ ╚════██║ ╚════██║');
    logger.info('╚██████╗ ██║  ██║ ██║  ██║    ██║    ╚███╔███╔╝ ███████║ ███████║');
    logger.info(' ╚═════╝ ╚═╝  ╚═╝ ╚═╝  ╚═╝    ╚═╝     ╚══╝╚══╝  ╚══════╝ ╚══════╝');
    logger.info('################ By Wn1027 ####### Qgroup:311860068 #############');
    // 开启Websocket服务端
    // 初始化外置玩家数据库
    if (SyncInventoryDB == '外置中心数据库'){
        logger.info(`跨服同步使用外置玩家数据库, 正在初始化数据库...`);
        CENTERDATA_db = new KVDatabase(`${SyncInventoryDBPath}/CENTERDATA`);
        CENTERTemp_db = new KVDatabase(`${SyncInventoryDBPath}/CENTERTemp`);
        // playersNBT_db = new KVDatabase(`${SyncInventoryDBPath}/playersNBT`);
        // playersOtherData_db =  new KVDatabase(`${SyncInventoryDBPath}/playersOtherData`);
        // playerInfo_db = new KVDatabase(`${SyncInventoryDBPath}/playerInfo`);
        syncFunc.autoBackup(); // 启动自动备份
    }

    wss = new Websocket.Server({host:"0.0.0.0", port: Port, verifyClient: ClientVerify});
    wss.on('connection', wsCallback);
    let taskid = setInterval(function(){
        if (wss.address() == null){return;}
        clearInterval(taskid);
        logger.info(`${ServerName}(ver:${String(ver)}) 已启动 |地址：${wss.address().address} |协议: ${wss.address().family} |端口: ${wss.address().port}`);
    }, 1000);
    // 启用控制台输入
    consoleInput();
}
//=======================================



//创建像素图片文件夹
if (fs.existsSync(imgFilePath) == false){
    fs.mkdirSync(imgFilePath,{recursive: true});
}
if (chatLog && fs.existsSync("./logs/chatwss/playerChat") == false){
    fs.mkdirSync("./logs/chatwss/playerChat", {recursive: true});
}


//心跳检测连接中断
const interval = setInterval(function ping() {
    mcws.forEach(function each(client) {
        if (client.userType == "client"){return;}
        if (client.ws.isLocal == true){return;}
        if (client.ws.isAlive === false) {
            logger.error("[Heartbeat] 一个客户端已失去连接");
            return client.ws.terminate();
        }
        client.ws.isAlive = false;
        client.ws.ping();
    });
}, 30000);

function heartbeat() {
  this.isAlive = true;
}

//========================================
// Websocket 连接

function ClientVerify(info) {
    let ret = false;//拒绝
    let params = url.parse(info.req.url, true).query;
    if (params["key"] == ConnectKey) {
        ret = true;//通过
    }
    return ret;  
}

function wsCallback(ws) { 
    ws.isAlive = true;
    ws.on('pong', heartbeat);

    //监听客户端消息
    ws.on('message', onMessage);

    var thisClient = new ClientClass.WsClient(mcws.usedUid + 1, ws);

    //登记客户端连接
    mcws.add(thisClient);

    //logger.info(`[新成员连入] Uid:${thisClient.uid}`);


    //Send-基本事件订阅
    events.forEach((event)=>{
        thisClient.subscribe(event);
    });
    

    //Send-获取昵称
    thisClient.runcmd("testfor @s", "initName");

    //Send-请求位置信息
    thisClient.runcmd("testforblock ~~~ air", "initPos");

    // Actionbar ❀
    var actionbar = setInterval(function () {
        if (thisClient.progress == 0){
            //mcws.runcmd(`title @p actionbar §d§l❀§b${ServerName}§d❀\n  === §6§l在线人数: §2§l${Object.keys(mcws).length} §d===`, thisClient);
        }else{
            thisClient.runcmd(`execute at "${thisClient.name}" as "${thisClient.name}" run title @s actionbar progress: ${thisClient.progress}%`);
        }
    }, 3000);


    //=============================================================    
    //监听接受消息
    function onMessage(message) {
        var Res = JSON.parse(message);
        
        var RequestId = {tag: null, uid:null};
        if ("requestId" in Res.header){
            RequestId = {tag: Res.header.requestId.split("@")[0], uid:Res.header.requestId.split("@")[1]};
            //console.log(RequestId.tag, " ", RequestId.uid);
            
        }

        //接受json消息显示
        if (jsonDisplay == true){
            console.log(Res);
        }

        //登录初始化
        connectInit(Res);

        //处理BDS功能
        BDServer(Res);

        //玩家数据同步
        playerDataSync(Res);

        // 云端聊天
        cloudChat(Res);

        // 像素画生成
        PixelsPlot(Res);

        // 其他云端命令
        cloundCmd(Res);

        // 命令发送失败debug
        function cmdfailed(Res){
            if (logger.query() >= 5){
                if (Res.header.messagePurpose ==  'commandResponse'){
                    if (Res.body["statusCode"] != 0){
                        console.log(Res);
                    }
                }
            }
        }
        
        // 登录初始化
        function connectInit(Res){	        
            if (["initPos", "initName"].indexOf(RequestId.tag) == -1){return;} //非初始化消息过滤
            if (thisClient.initState == 2){return;} // 排除已初始化的客户端
            thisClient["tasking"] = false;// 关闭可能未完成的命令
            
            //获取基本信息以及BDS初始化 昵称 及 客户端类型 | testfor @s 的返回
            if (RequestId.tag == "initName") {    
                thisClient.name = Res.body.victim[0];
                if (Res.body.userType == "server"){
                    thisClient.userType = "server"; // LLBDS插件连接
                    thisClient.BDSType = Res.body.BDSType; // 同步服务器的类型（主服务器还是副服务器,'main' or 'sub'）
                    thisClient.BDSUid = Res.body.BDSUid;
                    thisClient.BDSIp = Res.body.BDSIp;
                    thisClient.BDSPort = Res.body.BDSPort;
                    if (Res.body.BDSType == 'main'){
                        // 同步服务器只能有一个主服务器

                        let mainBDS = mcws.syncMainBDS;
                        if (mainBDS == null){
                            thisClient.BDSType = 'sub';
                            mainBDS = mcws.syncMainBDS;
                            logger.error(`[Sync][${ServerName}] ${thisClient.name}(${thisClient.uid}) 已更改为附属服务器 | 现在请手动去修改该附属服务器的ServerType配置为'sub'`);
                            logger.error(`[Sync][${ServerName}] 同步服务器只能有一个主服务器, 当前主服务器为: ${mainBDS.name}(${mainBDS.uid}) `);
                        }
                    }
                    
                }
                                thisClient.initState++;
            }

            //获取坐标 | testforblock ~~~ air 的返回
            if (RequestId.tag == "initPos") {
                //initPos testfor @s 请求返回必包括position 
                thisClient.locPos = Res.body.position;
                thisClient.initState++;
            }

            //成功登录
            if (thisClient.initState == 2){
                // 更新在线BDS信息
                if (thisClient.userType == "server"){
                    mcws.refOnlineBDSInfo();
                }
                
                logger.info(`『${thisClient.name}』 已连接 ${ServerName} | 类型: ${thisClient.userType} | Uid: ${thisClient.uid}`);
                //logger.info(`『${thisClient.name}』 坐标: ${thisClient.locPos.x}, ${thisClient.locPos.y}, ${thisClient.locPos.z}`);
                logger.info(`当前在线连接(${mcws.num}): ${mcws.OnlineNames}`);
                mcws.broadcast(`${msgHeader(thisClient.name)} §e已连接: §b${ServerName}`);
                thisClient.sendText(`${msgHeader(thisClient.name)} §e生成原点: §r${thisClient.locPos.x}, ${thisClient.locPos.y}, ${thisClient.locPos.z}`);
                thisClient.sendText(`${msgHeader(thisClient.name)} §e当前在线: §r${mcws.num}个连接`);
                thisClient.sendText(`${msgHeader(thisClient.name)} §7输入 ./help 查看帮助`);
            }
        }

        // 处理BDS功能（BDS玩家登录、登出、BDS广播消息（包括控制台））
        function BDServer(Res){
            if (Res.header.eventName != "BDServer"){return;}
            let bds_data = Res.body.data;
            let send_data;
            switch(bds_data.tag){

                // 玩家登录同步云端
                case "playerPreLogin":
                    send_data = {
                        tag: "playerLogin",
                        eventid: bds_data.eventid,
                        success: mcws.playerPreLogin(thisClient, bds_data.playerRealName), //记录玩家，同时也判断了该玩家多设备状态，不允许重复登录
                        loginTime: Number(new Date()),
                        playerRealName: bds_data.playerRealName,
                        playerXuid: bds_data.playerXuid,
                        playerUuid: bds_data.playerUuid,
                        requestClientUid: thisClient.uid,
                        requestClientName: thisClient.name
                    };
                    thisClient.sendJson(send_data, 'BDServer'); //向BDS返回协调服务端登录情况
                    logger.debug(`[BDS][${ServerName}][玩家登录中(2)] <${bds_data.playerRealName}> 请求预登录云端, 检查该玩家: ${send_data.success}`);
                    break;

                case "playerLogin":
                    send_data = {
                        tag: "playerLogin",
                        eventid: bds_data.eventid,
                        success: mcws.playerLogin(thisClient, bds_data.playerRealName), //记录玩家，同时也判断了该玩家多设备状态，不允许重复登录
                        loginTime: Number(new Date()),
                        playerRealName: bds_data.playerRealName,
                        playerXuid: bds_data.playerXuid,
                        playerUuid: bds_data.playerUuid,
                        requestClientUid: thisClient.uid,
                        requestClientName: thisClient.name
                    };
                    thisClient.sendJson(send_data, 'BDServer'); //向BDS返回协调服务端登录情况
                    logger.debug(`[BDS][${ServerName}][玩家登录中(2)] <${bds_data.playerRealName}> 请求登录云端, 登记该玩家: ${send_data.success}`);
                    break;

                // 玩家退出同步云端
                case "playerLogout":
                    send_data = {
                        tag: "playerLogout",
                        eventid: bds_data.eventid,
                        success: mcws.playerLogout(thisClient, bds_data.playerRealName), // 取消该玩家的云端在线状态
                        logoutTime: Number(new Date()),
                        playerRealName: bds_data.playerRealName,
                        playerXuid: bds_data.playerXuid,
                        playerUuid: bds_data.playerUuid,
                        requestClientUid: thisClient.uid,
                        requestClientName: thisClient.name
                    };
                
                    thisClient.sendJson(send_data, 'BDServer');
                    logger.debug(`[BDS][${ServerName}] <${bds_data.playerRealName}> 请求退出云端, 登出该玩家: ${send_data.success}`);
                    break;

                // 回复当前协调服务端在线玩家
                case "onlinePlayers":
                    send_data = {
                        tag: "onlinePlayers",
                        eventid: bds_data.eventid,
                        onlinePlayers: mcws.onlinePlayers
                    };
                    mcws.broadcastJson(send_data, 'BDServer');
                    break;

                // 同步消息广播
                case "broadcast":
                    // 若ws服务端独立运行，则记录一下广播的控制台消息
                    if (LocalClientState == false){
                        logger.info(bds_data.consoleMsg);
                    }
                    mcws.broadcastJson(bds_data, 'BDServer');
                    break;
            }
            return;
        }

        // 玩家数据同步
        function playerDataSync(Res){
            if (Res.header.eventName != "Sync"){return;}
            let sync_data = Res.body.data;
            let mainBDS = mcws.syncMainBDS;
            let send_data;
            let client_1;
            switch(sync_data.tag){

                // [数据请求] 向同步BDS服务器中的主服务器请求玩家数据
                case "getPlayerData":
                    //外置玩家数据库
                    if (mainBDS == null && SyncInventoryDB == '外置中心数据库'){
                        (async ()=>{
                            let playerData = await syncFunc.getOfflinePlayerSyncData(sync_data.playerUuid);
                            if (playerData == null){ // 新玩家初始化外置数据库
                                let res = await syncFunc.setPlayerData(sync_data.playerData);
                                if (res == true){
                                    logger.debug(`[Sync][${ServerName}][玩家登录中(4)][数据请求] 新玩家 <${sync_data.playerRealName}>, 初始化数据 `);
                                    playerData = sync_data.playerData;
                                }else{
                                    send_data = {
                                        tag: "playerData",
                                        eventid: sync_data.eventid,
                                        success: false,
                                        reason: "error",
                                        output: `外置玩家数据库在读取玩家数据时发生了错误`,
                                        playerRealName: sync_data.playerRealName,
                                        playerUuid: sync_data.playerUuid,
                                        playerXuid: sync_data.playerXuid,
                                        requestClientUid: sync_data.requestClientUid,
                                        requestClientName: sync_data.requestClientName,
            
                                    };
                                    thisClient.sendJson(send_data, 'Sync');
                                    logger.error(`[Sync][${ServerName}][玩家登录中(4)][数据请求] 初始化新玩家 <${sync_data.playerRealName}> 失败，外置玩家数据库在写入玩家数据时发生了错误`);
                                    return;
                                }
                            }
                            //await playersNBT_db.get(sync_data.playerXuid);
                            send_data = {
                                tag: "playerData",
                                eventid: sync_data.eventid,
                                success: true,
                                playerData: playerData,
                                playerUuid: sync_data.playerUuid,
                                playerXuid: sync_data.playerXuid,
                                playerRealName: sync_data.playerRealName,
                                // mainBDSName: ServerName, （无外置时，转发的主服玩家数据项，这里不需要）
                                // mainBDSCnnUid: sync_data.mainBDSCnnUid,
                                // requestClientUid: sync_data.requestClientUid,
                                // requestClientName: sync_data.requestClientName
                            };
                            thisClient.sendJson(send_data, 'Sync');
                            logger.debug(`[Sync][${ServerName}][玩家登录中(4)][数据请求] (${Buffer.byteLength(JSON.stringify(send_data), 'utf8')} bytes) 回复外置数据库玩家数据 <${sync_data.playerRealName}>`);
                        })();
                        break;
                    }

                    // 返回数据tag: playerData
                    if (mainBDS != null){
                        //有主服务器
                        send_data = {
                            tag: "getPlayerData",
                            eventid: sync_data.eventid,
                            playerUuid: sync_data.playerUuid,
                            playerXuid: sync_data.playerXuid,
                            playerRealName: sync_data.playerRealName,
                            playerData:{
                                BDSUid: thisClient.BDSUid
                            },
                            mainBDSName: mainBDS.name,
                            mainBDSCnnUid: mainBDS.uid,
                            requestClientUid: thisClient.uid,
                            requestClientName: thisClient.name
                        };
                        mainBDS.sendJson(send_data, 'Sync');
                        logger.debug(`[Sync][${ServerName}][玩家登录中(4)][数据请求] 向主服务器(${mainBDS.name} ${mainBDS.uid})请求玩家数据 <${sync_data.playerRealName}>`);
                    }else{
                        //没有主服务器
                        send_data = {
                            tag: "playerData",
                            eventid: sync_data.eventid,
                            success: false,
                            reason: "noMainBDS",
                            output: `从主服务器同步失败，没有找到主服务器，请联系服主`,
                            playerRealName: sync_data.playerRealName,
                            playerUuid: sync_data.playerUuid,
                            playerXuid: sync_data.playerXuid,
                            requestClientUid: sync_data.requestClientUid,
                            requestClientName: sync_data.requestClientName,

                        };
                        thisClient.sendJson(send_data, 'Sync');
                        logger.warn(`[Sync][${ServerName}][玩家登录中(4)][数据请求] 向主服务器请求玩家数据失败, 没有找到主服务器 <${sync_data.playerRealName}>`);
                    }
                    break;

                // [数据请求] 请求玩家数据得到主服务器回应，将请求结果发回给请求数据的BDS
                case "playerData":
                    client_1 = mcws.getClient_uid(sync_data.requestClientUid);
                    client_1.sendJson(sync_data, 'Sync');
                    logger.debug(`[Sync][${ServerName}][玩家登录中(6)][数据请求] (${Buffer.byteLength(JSON.stringify(sync_data), 'utf8')} bytes) 转发主服务器回复的玩家数据 <${sync_data.playerRealName}>`);
                    break;		

                // [数据同步] 请求主服务器进行同步
                case "syncPlayerData":
                    //外置玩家数据库
                    if (mainBDS == null && SyncInventoryDB == '外置中心数据库'){
                        (async ()=>{
                            let result = await syncFunc.setPlayerData(sync_data.playerData);
                            if (result == true){
                                send_data = {
                                    tag: "confirmSyncPlayerData",
                                    eventid: sync_data.eventid,
                                    success: true,
                                    // mainBDSName: ServerName, (无外置数据库时的确认保存项，这里不需要)
                                    playerXuid: sync_data.playerData.playerXuid,
                                    playerRealName: sync_data.playerData.playerRealName,
                                    // mainBDSCnnUid: sync_data.mainBDSCnnUid,
                                    // requestClientUid: sync_data.requestClientUid,
                                    // requestClientName: sync_data.requestClientName
                                };
                                thisClient.sendJson(send_data, 'Sync');
                                logger.debug(`[Sync][${ServerName}][数据保存] 外置数据库更新玩家数据完成 <${sync_data.playerData.playerRealName}>`);
                            }else{
                                send_data = {
                                    tag: "confirmSyncPlayerData",
                                    eventid: sync_data.eventid,
                                    success: false,
                                    output: `[Sync][${ServerName}][数据保存] 更新玩家数据失败, 外置玩家数据库写入数据时发生了错误 <${sync_data.playerData.playerRealName}>`,
                                    playerData: sync_data.playerData,
                                    playerXuid: sync_data.playerData.playerXuid,
                                    playerRealName: sync_data.playerData.playerRealName,
                                    requestClientUid: thisClient.uid,
                                    requestClientName: thisClient.name
                                };
                                thisClient.sendJson(send_data, 'Sync');
                                logger.error(`[Sync][${ServerName}][数据保存] 更新玩家数据失败, 外置玩家数据库写入数据时发生了错误 <${sync_data.playerData.playerRealName}>`);
                            }
                            
                        })();
                        break;
                    }
                    // 有主服务器
                    if (mainBDS != null){
                        send_data = {
                            tag: "syncPlayerData",
                            eventid: sync_data.eventid,
                            playerData: sync_data.playerData, //待同步的数据
                            syncConfig: sync_data.syncConfig,
                            mainBDSName: mainBDS.name,
                            mainBDSCnnUid: mainBDS.uid,
                            requestClientUid: thisClient.uid,
                            requestClientName: thisClient.name
                        };

                        mainBDS.sendJson(send_data, 'Sync');
                        logger.debug(`[Sync][${ServerName}][数据保存] 请求主服务器(${mainBDS.name} ${mainBDS.uid}) 更新玩家数据 <${sync_data.playerData.playerRealName}>`);
                    }

                        //没有主服务器
                    else{
                        send_data = {
                            tag: "confirmSyncPlayerData",
                            eventid: sync_data.eventid,
                            success: false,
                            output: `[Sync][${ServerName}][数据保存] 更新玩家数据失败, 没有找到主服务器 <${sync_data.playerData.playerRealName}>`,
                            playerData: sync_data.playerData,
                            playerXuid: sync_data.playerData.playerXuid,
                            playerRealName: sync_data.playerData.playerRealName,
                            requestClientUid: thisClient.uid,
                            requestClientName: thisClient.name
                        };
                        thisClient.sendJson(send_data, 'Sync');
                        logger.warn(`[Sync][${ServerName}][数据保存] 更新玩家数据失败, 没有找到主服务器 <${sync_data.playerData.playerRealName}>`);
                    }
                    break;
                
                // [数据同步] 转发主服务器保存结果
                case "confirmSyncPlayerData":
                    client_1 = mcws.getClient_uid(sync_data.requestClientUid);
                    client_1.sendJson(sync_data, 'Sync');
                    logger.debug(`[Sync][${ServerName}][数据保存] 转发主服务器(${mainBDS.name} ${mainBDS.uid}) 更新玩家数据的结果 <${sync_data.playerRealName}>`);
                    break;
                
                // [LLMoney同步] 请求主服务器实时同步llmoney
                case "llmoney":
                    mcws.broadcastJson(sync_data, 'Sync', [thisClient.uid]);
                    logger.debug(`[Sync][${ServerName}][数据保存] 更新玩家(${sync_data.playerXuid}) LLmoney 为 ${sync_data.value}`);
                    break;
            }
        }

        // 云端聊天
        function cloudChat(Res){
            if (Res.header.eventName == "PlayerMessage"){
                let sender = Res.body.sender;
                let sendmsg = Res.body.message;
                let type = Res.body.type;

                //监听本连接玩家消息
                if ((sendmsg.substring(0, 1) == chatHeader || chatHeader == "") && sendmsg.substring(0, 2) != `{"` && type == "chat"){
                    if (sendmsg.substring(0, 1) == chatHeader){
                        sendmsg = sendmsg.slice(1);
                    }
                    
                    if (thisClient.userType == "client"){
                        mcws.broadcast(`${msgHeader(sender)} §a${sendmsg}`); //添加chat参数则以聊天消息形式发送
                    }else if(thisClient.userType == "server"){
                        mcws.broadcast(`${msgHeader(sender, thisClient.name)} §a${sendmsg}`, "tellraw", [thisClient.uid]); // 广播聊天消息不包括自己 [thisClient.uid]
                    }	                
                    // 记录云端聊天日志
                    // fs.writeFile('./chatlog.txt', `${msgHeader(sender)} §a${sendmsg}\n`, { 'flag': 'a' }, function (err) {
                    //     if (err) {
                    //         throw err;
                    //     }
                    // });
                    if (chatDisplay == true){//WS协调服务端控制台聊天显示
                        logger.info(`[${thisClient.name}]<${sender}> ${sendmsg}` );
                    }
                    if (chatLog){
                        logger.write(`[${thisClient.name}]<${sender}> ${sendmsg}`);
                    }                    
                    return;
                }
                // if (  "sender" in Res.body && sendmsg.substring(0, 4) != "{\"ra" ) //过滤json提示消息
            }
        }

        // 像素画生成
        function PixelsPlot(Res){
            if (Res.header.eventName == "PlayerMessage"){
                let sender = Res.body.sender;
                let sendmsg = Res.body.message;

                // 终止命令
                if (sendmsg == "./stoptask"){
                    thisClient.tasking = false;
                    thisClient.sendText(`${tipHeader} §c终止生成`);
                }

                // paint命令
                if (sendmsg.split(" ")[0] == "./paint"){
                    let imgFileName = sendmsg.split(" ")[1];

                    // 没有输入文件名
                    if (imgFileName == undefined){
                        thisClient.sendText(`${tipHeader} §c文件名错误`);
                        return;
                    }

                    // 后缀错误
                    if (["jpg","png"].indexOf(imgFileName.slice(-3)) == -1){
                        thisClient.sendText(`${tipHeader} §c文件名后缀错误，须为.jpg或.png`);
                        return;
                    }

                    // flat、uneven 参数错误
                    if (sendmsg.split(" ")[2] == undefined || sendmsg.split(" ")[2] == "flat"){
                        thisClient.mapcolor = mapcolor_lab_flat;
                    }else if(sendmsg.split(" ")[2] == "uneven"){
                        thisClient.mapcolor = mapcolor_lab;
                    }else{
                        thisClient.sendText(`${tipHeader} §c参数错误，须为flat或uneven |（flat为平面，uneven为立体）`);
                        return;
                    }

                    // chunk绘制步长数字错误
                    if (sendmsg.split(" ")[3] != undefined){
                        if ((/(^[0-9]\d*$)/.test(sendmsg.split(" ")[3]))){
                            if (Number(sendmsg.split(" ")[3]) > 128 || Number(sendmsg.split(" ")[3]) < 32){
                                thisClient.sendText(`${tipHeader} §c第三个参数应为32-128的数字`);
                                return;
                            }
                            thisClient.ChunkStep = Number(sendmsg.split(" ")[3]);
                        }else{
                            thisClient.sendText(`${tipHeader} §c第三个参数应为32-128的数字`);
                            return;
                        }
                    }
                    

                    // 单一任务进程
                    if (thisClient.tasking == true){
                        thisClient.sendText(`${tipHeader} §c当然有任务进行中`);
                        return;
                    }
                    
                    // 执行绘制像素画
                    fs.exists(`${imgFilePath}/${imgFileName}`,(e)=>{
                        if (e == false){
                            thisClient.sendText(`${tipHeader} §c未找到 ${imgFileName}`);
                            return;
                        }
                        //选取当前地点的生成原点
                        get_origin(64, thisClient);

                        logger.info(`<${thisClient.name}> 请求生成像素画: ${imgFilePath}/${imgFileName}`);
                        thisClient.sendText(`${tipHeader} §e处理图片中...`);
                        thisClient.sendText(`${tipHeader} §a生成原点: §r${thisClient.locPos.x}, ${thisClient.locPos.y}, ${thisClient.locPos.z}`);
                        
                        //开始生成
                        getPixels(`${imgFilePath}/${imgFileName}`,(err, pixels)=>getPixelsMap(err, pixels, imgFileName, thisClient));
                        //getPixels(`${imgFilePath}/${imgFileName}`, getPixelsMap);
                        //getPixels(`${imgFilePath}/${imgFileName}`,function (p){getPixelsMap(p,thisClient);});
                        
                    });
                }
            }	
        }

        // 其他云端命令
        function cloundCmd(Res){
            // 命令请求
            if (Res.header.eventName == "PlayerMessage"){
                let sender = Res.body.sender;
                let sendmsg = Res.body.message;

                // 查询在线
                if (sendmsg == ".online"){
                    thisClient.sendText(`${tipHeader} §e当前在线: §r${mcws.num}个连接`);
                    thisClient.sendText(`${tipHeader} ${mcws.OnlineNames}`);
                }

                // loc定位命令
                if (sendmsg == "./loc"){
                    thisClient.runcmd("testforblock ~~~ air", "locatePos");
                }

                // help命令
                if (sendmsg == "./help"){
                    thisClient.sendText(HelpInfo);
                }

            }
            // 命令回调
            if (RequestId.tag == "locatePos"){
                if ("position" in Res.body) {
                    thisClient.locPos = Res.body.position;
                    thisClient.sendText(`${tipHeader} §a定位生成原点: §r${thisClient.locPos.x}, ${thisClient.locPos.y}, ${thisClient.locPos.z}`);
                }
            }
        }
    }
    //关闭链接提示
    ws.on('close', function (){
        mcws.broadcast(`${msgHeader(thisClient.name)} §7已断开 ${ServerName}`);
        mcws.remove(thisClient);
        
        logger.info(`『${thisClient.name}』 | 类型: ${thisClient.userType} | Uid: ${thisClient.uid} 断开了云端连接`);
        logger.info(`当前在线连接(${mcws.num}): ${mcws.OnlineNames}`);
        
        if (thisClient.userType == "server"){
            if (thisClient.BDSType == "main"){
                mcws.broadcastJson('BDServer', {
                    tag: 'mainBDSdisconnect',
                    mainBDSName: thisClient.name
                });
                mcws.onlinePlayers = {}; // 主服断开，所有玩家都下线
            }
            // 更新在线BDS信息
            mcws.refOnlineBDSInfo();
        }
    });
}

// 获取生成原点
function get_origin(shape, thisClient){
    let pos = thisClient.locPos;
    let pos_origin = {x:257,y:pos.y,z:257};
    if (shape == 128){//2级地图，对应256*256方块
        pos_origin.x = Math.floor((pos.x-64) / 256) * 256 + 64;
        pos_origin.z = Math.floor((pos.z-64) / 256) * 256 + 64;
        return pos_origin;
    }else if (shape == 64){ //1级地图，对应128*128方块
        pos_origin.x = Math.floor((pos.x-64) / 128) * 128 + 64;
        pos_origin.z = Math.floor((pos.z-64) / 128) * 128 + 64;
        return pos_origin; 
    }else{
        //log("get_origin(pos,shape) : wrong shape");
    }
    thisClient.locPos = pos_origin;
}

// 控制台输入
function consoleInput(){
    process.stdin.resume();
    process.stdin.setEncoding('utf8');
    process.stdin.on('data', function (text) {
        var cmd = text.split(/\s+/)[0];
        var args = text.split(/\s+/).slice(1);
        let res;
        switch (cmd){
            case 'help':
                res = '  # WS协调服务端指令帮助 #';
                res += '\n - dspchat: 聊天显示';
                res += '\n - dspjson: JSON显示';
                res += '\n - setlog <level>: 屏幕输出日志等级(2-5)';
                res += '\n - list: 显示在线连接';
                res += '\n - eval: 执行代码';
                res += '\n - backup: 备份外置中心数据库';
                res += '\n - stop: 关闭WS协调服务端';
                res += '\n - broadcastAllPlayerData: (使用前务必备份所有BDS存档)向当前在线的所有BDS导出 外置中心数据库 的所有玩家数据';
                logger.info(res);
                break;
            case 'broadcastAllPlayerData':
                syncFunc.broadcastAllPlayerData();
                break;
            case 'backup':
                if (SyncInventoryDB == '外置中心数据库'){
                    syncFunc.backup();
                }
                break;
            case 'stop':
                done();
                break;

            case 'dspchat':
                chatDisplay = !chatDisplay;
                if (chatDisplay == true){
                    logger.warn(`已开启聊天显示`);
                }else{
                    logger.warn(`已关闭聊天显示`);
                }
                break;

            case 'dspjson':
                jsonDisplay = !jsonDisplay;
                if (jsonDisplay == true){
                    logger.warn(`已开启json显示`);
                }else{
                    logger.warn(`已关闭json显示`);
                }
                break;

            case 'setlog':
                logger.setConsole(true, args[0]);
                break;

            case 'eval':
                try{
                    eval(args.join(" "));
                }catch(e){
                    logger.error( e );
                }
                break;

            case 'list':
                let OnlinePlayers = mcws.onlinePlayers;
                let plCount = 0;
                let listRes = '';
                for (let key in OnlinePlayers){
                    listRes += `\n\n[${OnlinePlayers[key].ServerName}] \n${OnlinePlayers[key].players.join(', ')}`;
                    plCount += OnlinePlayers[key].players.length;
                }
                listRes = `当前在线(${plCount}):` + listRes.slice(1);
                logger.info(listRes);
                break;
                
            default:
                logger.error(`"${cmd}" not a command for chatwss.`);
                break;
        }	  	
    });

    function done() {
      console.log('Websocket server quit.');
      process.exit();
    }
}
// eval (async ()=>{console.log(await CENTERDATA_db.get("2535427849428692"))})();